Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature proposal: blocking="render" attribute on <link>, <script> and <style> #7131

Closed
xiaochengh opened this issue Sep 29, 2021 · 17 comments
Closed
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest topic: rendering

Comments

@xiaochengh
Copy link
Contributor

xiaochengh commented Sep 29, 2021

(Edit: The syntax is changed into blocking="render" for extensibility. )

(Please refer to the full proposal for formal details and more discussion)

Explainer

All current browsers already have a render-blocking mechanism: after navigation, the user agent will not render any pixel to the screen before all stylesheets and synchronous scripts in <head> are loaded and evaluated (or a UA-defined timeout is reached)1. This prevents a Flash of Unstyled Contents (FOUC) and ensures critical scripts (like framework code) are executed, so that the page is usable after the first rendering cycle.

In this proposal, we extend the above idea and propose a new attribute blocking that can be added to <link>, <script> and <style> elements in <head>. For now, we would only allow one value blocking="render" to support the most demanding use cases, but we would also like to keep the syntax open for future extensions.

Use cases

  • Block rendering on a critical web font to prevent layout shifting or a flash of invisible/unstyled text (see here for more details). It also works in the preloading of critical resources of other types, like images, json, etc.
<link blocking="render" rel="preload" href="critical-font.woff2"
      as="font" type="font/woff2" crossorigin>
  • Block rendering on script-inserted stylesheets or scripts. This prevents a FOUC when, e.g., the page uses a loader script to load the actual business stylesheets:
<script>
let link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'business-style.css';
link.setAttribute('blocking', 'render');
document.head.appendChild(link);
</script>
  • Block rendering on an async script, so that the script doesn’t block parsing but is guaranteed to be evaluated before rendering starts. This is useful for, e.g., SPA loader and A/B test framework scripts, which can be loaded asynchronously but need control over what is displayed on the first render.
<script blocking="render" async src="async-script.js"></script>
  • Besides, while rendering is blocked, requestAnimationFrame() callbacks should not be fired2. So we can add a requestAnimationFrame() in <head> to perform some tasks right before the first rendering cycle to, e.g., ensure a better-looking first paint:
<script>
requestAnimationFrame(() => {
  let status = getResourceLoadingStatus();
  putProgressBarOnPendingResources(status);
});
</script>

Footnotes

[1] The exact behaviors differ slightly.
[2] Current browsers have different behaviors, and this proposal would like to standardize it. See test case.

@chrishtr chrishtr added the agenda+ To be discussed at a triage meeting label Oct 7, 2021
@annevk annevk added addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest topic: rendering labels Oct 7, 2021
@alystair
Copy link

alystair commented Oct 7, 2021

Should consider how this relates to HTTP link header preloads, as well as upcoming 103 Early Hints.

From personal experience FOUC is a non-issue using the HTTP header equiv. Unsure about how the implementation differs, can't find spec.

... also wondering potential fun edge cases /w stuff like content-visibility :D

@yoavweiss
Copy link
Collaborator

Without expanding the scope of this, I think it may be interesting to look at an alternative spelling (e.g. blocking=render) that'd enable future extensibility to imply that resources should be blocking other events in the page's lifetime.

@past past removed the agenda+ To be discussed at a triage meeting label Oct 11, 2021
@khushalsagar
Copy link
Contributor

One aspect which is worth thinking through in the context of this proposal is ensuring the browser fetches and parses enough of the Document before rendering. It's unclear to me how the browser decides when the parser should yield for first render.

@xiaochengh xiaochengh changed the title Feature proposal: renderblocking attribute on <link> and <script> Feature proposal: blocking="render" attribute on <link> and <script> Nov 13, 2021
@xiaochengh
Copy link
Contributor Author

The proposal has been updated: Instead of a boolean attribute, it's now a string attribute blocking. For this proposal, we allow only one value blocking="render" to indicate render-blocking resource. The semantics and usage remain the same as before.

The syntax is intentionally left open for future extensions (so we may ultimately have something like blocking="!parse render documentcontentloaded !load").

For details, please refer to the full version, which also has addressed some feedbacks that we have received so far.

@zcorpan
Copy link
Member

zcorpan commented Nov 15, 2021

Thanks for the update.

While the current proposal only allows for render, it would be good to specify processing requirements of the attribute value in a way that is compatible with the envisioned future extensions. For example, if you use blocking="unknown render !unknown-2", implementations that support "v1" should pick up the render keyword and ignore the others.

I suggest using "split on ASCII whitespace", same as e.g. <link rel>

@xiaochengh
Copy link
Contributor Author

xiaochengh commented Nov 16, 2021

Good idea! I'll update the proposal

Edit: Updated. Now the syntax is a set of space-separated tokens, and the UA should ignore all unrecognized tokens.

@chrishtr chrishtr added the agenda+ To be discussed at a triage meeting label Nov 16, 2021
@zcorpan
Copy link
Member

zcorpan commented Nov 23, 2021

Looks good 👍

On this point:

For example, the element is not disabled, media query must match, ...

Can this be expanded?

@xiaochengh
Copy link
Contributor Author

I'll expand this part into a section. Time to go through every attribute to see how they work together :)

@xiaochengh
Copy link
Contributor Author

Proposal updated with more details filled in:

  1. A detailed proposal on the general render-blocking mechanism is added. This should also resolve Define rendering behavior of style sheet loading #3355
  2. Expanded how the UA should decide if putting blocking="render" on an element is effective or not. Basically, it's decided right before we start a fetch, so we don't need to duplicate how the other attributes work. I've finished on <link> and am still working on <script>. This should also resolve @zcorpan's previous comment.

The latest full version is at here.

@domenic
Copy link
Member

domenic commented Dec 2, 2021

The detailed proposal is looking quite good. In terms of other render-blocking resources, I think modulepreload should get the same treatment.

I wonder about prefetch and prerender, but probably those don't make sense as render-blocking. Thoughts welcome.

@annevk
Copy link
Member

annevk commented Dec 3, 2021

Per the triage call the style element should also be considered for inclusion.

@zcorpan
Copy link
Member

zcorpan commented Dec 8, 2021

@domenic prefetch and prerender are about the next navigation, so I think indeed doesn't make sense to be blocking the current page.

@xiaochengh
Copy link
Contributor Author

I'll update the proposal to include <style> and <link rel="modulepreload">. Will also add the render-blocking mechanism to the Fetch algorithm directly per the triage call.

@annevk
Copy link
Member

annevk commented Dec 13, 2021

Can you elaborate on that last sentence? It was my impression that HTML could manage this itself through a counter on Document objects.

@domenic
Copy link
Member

domenic commented Dec 13, 2021

Right, so I think what @annevk and I were envisioning was something like this:

  • Every Document has a set of render-blocking resources
  • Various operations (e.g. adding a blocker="render" attribute, changing the value of a blocking="" attribute, disconnecting the elements from the DOM, ...) add or remove elements to this set
  • Finishing loading will also remove the corresponding element from the set.
  • The rendering part of the event loop processing model consults this set. If it is empty, it can render. Otherwise, it cannot render (i.e. it skips a bunch of rendering-related steps).
  • Probably after an implementation-defined timeout, the user agent is allowed to clear the set (and thus render anyway).

Then in the future we could have something where fetch() adds/removes a request to the set, and it would all work well for fetch() just like it does for <link>, <script>, and <style>. I think that's what we were discussing on the call mostly.

It's possible we could get away with a counter instead of a set. However, that might require some tricky bookkeeping in some cases to ensure that we never decrement twice for the same resource (e.g., decrement once for removing the blocking="" attribute, and once for finishing loading). A set is probably a bit easier to spec. But a counter is probably easier to implement, so if we can spec a counter that might make the spec nicer for implementers.

@Yay295
Copy link
Contributor

Yay295 commented Dec 13, 2021

Should probably also specify that adding this attribute after the resource has loaded won't add the element to the set.

@xiaochengh
Copy link
Contributor Author

Thanks @domenic!

I was thinking about allowing fetch() to directly manage the render-blocking set (as per the call), but after some more thoughts it seems too much for an MVP. So I'll put it as a possible future extension.

Should probably also specify that adding this attribute after the resource has loaded won't add the element to the set.

This should be satisfied already, because we process this attribute only when we start fetching.

@xiaochengh xiaochengh changed the title Feature proposal: blocking="render" attribute on <link> and <script> Feature proposal: blocking="render" attribute on <link>, <script> and <style> Jan 14, 2022
mfreed7 pushed a commit to mfreed7/html that referenced this issue Jun 3, 2022
Closes whatwg#7131.

The new render-blocking mechanism is used to specify cases where <script> and <link> can be render-blocking in all browsers, but were not according to the spec. This closes whatwg#3355 (although note that whatwg#696 remains open).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest topic: rendering
Development

No branches or pull requests

11 participants